import { Inject, Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';
import { ResponseMsgKey } from 'src/common/constants/api-response.constant';
import { AccountErrorKey } from '../../../common/constants/user.constant';
import { getIpAddress, IpUtil } from '../../../common/utils/ip.util';
import { SvgCaptcha } from '../../../common/utils/svg-captcha.util';
import {
  generateCaptchaKey,
  generateLoginExceptionKey,
  RedisService,
} from '../../shared/redis/redis.service';
import { Role } from '../role/entities/role.entity';
import { AccountType, LoginLog } from './entities/login-log.entity';
import { convertSecondsToTime } from '../../../common/utils/date.util';
import { I18nContext, I18nService } from 'nestjs-i18n';
import { ResponseUtil } from '../../../common/utils/response.util';
import { encrypt } from '../../../common/utils/rsa.util';
import { ConfigService } from '@nestjs/config';

@Injectable()
export class AuthService {
  /** 登录异常重试次数 */
  private static readonly allowedLoginErrorCount: number = 5;

  private readonly isDev: boolean;

  constructor(
    private readonly configService: ConfigService,
    private readonly jwtService: JwtService,
    private readonly redisService: RedisService,
    @Inject(I18nService)
    private readonly i18n: I18nService,
  ) {
    this.isDev = this.configService.get('IS_DEV') === 'true';
  }

  generateToken(id: string, roles: Role[]) {
    return this.jwtService.sign({ id, roleIds: roles.map((r) => r.id) });
  }

  /**
   * 获取token并创建账号登录记录
   * @param accountId
   * @param accountRoles
   * @param request
   * @param accountType
   * @returns
   */
  async getTokenAndCreateLoginLog(
    accountId: string,
    accountRoles: Role[],
    request: Request,
    accountType: AccountType,
  ) {
    const ip = IpUtil.getRealIP(request);
    await this.redisService.del(generateLoginExceptionKey(ip));
    const loginAddress = await getIpAddress(ip);
    await LoginLog.save(
      LoginLog.create({
        accountId,
        accountType,
        loginIp: ip,
        loginAddress,
      }),
    );
    return this.generateToken(accountId, accountRoles);
  }

  /**
   * 生成图形验证码
   * @returns
   */
  async getLoginCaptcha() {
    const captcha = SvgCaptcha.generateCaptcha();
    const code = captcha.text.toLowerCase();
    const key = generateCaptchaKey(code);
    await this.redisService.set(key, code, 'EX', 60 * 3);
    return { key, captcha };
  }

  /**
   * 比较并删除缓存图形验证码
   * @param captcha
   */
  async checkAndDeleteCaptcha(captcha: string) {
    const key = generateCaptchaKey(captcha);
    const code = await this.redisService.get(key);
    await this.redisService.del(key);
    if (!code || code !== captcha.toLocaleLowerCase()) {
      throw new Error(AccountErrorKey.VERIFY_CODE_ERROR);
    }
  }

  /**
   * 记录登录异常次数并返回剩余重试次数
   * @param ip 请求登录接口IP
   * @returns 剩余重试次数
   */
  async setLoginIpBlackList(ip: string) {
    const loginExceptionKey = generateLoginExceptionKey(ip);
    let count = parseInt(await this.redisService.get(loginExceptionKey)) || 0;
    await this.redisService.set(loginExceptionKey, ++count, 'EX', 60 * 60 * 2);
    return AuthService.allowedLoginErrorCount - count;
  }

  /**
   * 检查请求登录接口IP剩余重试次数
   * @param ip 请求登录接口IP
   * @returns
   */
  async checkLoginIpBlackList(ip: string) {
    const loginExcepitonKey = generateLoginExceptionKey(ip);
    const count = parseInt(await this.redisService.get(loginExcepitonKey)) || 0;
    if (count >= AuthService.allowedLoginErrorCount) {
      const time = await this.redisService.ttl(loginExcepitonKey);
      const { days, hours, minutes, secs } = convertSecondsToTime(time);
      const message = this.i18n.translate(
        `${ResponseUtil.index}.${ResponseMsgKey.FORBID_IP}`,
        {
          lang: I18nContext.current().lang,
          args: {
            ['days']: days,
            ['hours']: hours,
            ['minutes']: minutes,
            ['secs']: secs,
          },
        },
      );
      throw new Error(message);
    }
  }

  rsaEncrypt(data: any) {
    let response = data;
    if (this.isDev) {
      response = encrypt(data);
    }
    return response;
  }
}
